﻿#if UNITY_EDITOR || COMMAND_PROFILER
#define PROFILER
#endif
using System.Collections.Generic;
using Devil;

#if UNITY_EDITOR
using UnityEditor;
#endif

#if PROFILER
using UnityEngine.Profiling;
#endif

using UnityEngine;

namespace GameToolkit
{
    public enum EExecuteEvent
    {
        SubThread,
        Update,
        LateUpdate,
        FixedUpdate,
    }

    public partial class MainThread
    {
        private readonly static object sLock = new object();
        private static HashSet<int> sDuplicateTaskCache = new HashSet<int>();
        private static HashSet<int> sDuplicateFixedCache = new HashSet<int>();
        private static Queue<ICommand> sTasks = new Queue<ICommand>(64);
        private static Queue<ICommand> sLateTasks = new Queue<ICommand>(16);
        private static Queue<ICommand> sFixedTasks = new Queue<ICommand>(64);
        private static bool sExecuting;
        private static bool sExecBuffer;
        private static HashSet<ExecutionBuffer> sBuffers = new HashSet<ExecutionBuffer>();
        static bool isSetup;

#if UNITY_EDITOR
        [InitializeOnLoadMethodParallel(IsOnMainThread = true)]
        static void AutoInstall()
        {
            //sMainThreadId = Thread.CurrentThread.ManagedThreadId;
            EditorApplication.update += EditorExecute;
        }

        static void EditorExecute()
        {
            if (Instance == null)
            {
                ExecuteTask();
                ExecuteLateTask();
                ExecuteFixedTask();
            }
        }

#endif
        public static void Setup()
        {
            if (!isSetup)
            {
#if UNITY_EDITOR
                if (Application.isPlaying)
                {
#endif
                    isSetup = true;

                    Setup<MainThread>("[Main Thread & Parallel Commands]");
                    //SubThreading.InitApplicationData();
#if UNITY_EDITOR
                }
#endif
            }
        }

        public static bool RunOnMainThread(ICommand command, EExecuteEvent type = EExecuteEvent.Update, bool enableRepeatExecute = false)
        {
            if (command != null)
            {
                lock (sLock)
                {
                    var id = command.GetHashCode();
                    if (sExecuting)
                    {
                        var buf = new ExecutionBuffer(id, type, command);
                        sExecBuffer = true;
                        return sBuffers.Add(buf);
                    }
                    else
                    {
                        var cache = type == EExecuteEvent.FixedUpdate ? sDuplicateFixedCache : sDuplicateTaskCache;
                        if (!enableRepeatExecute && !cache.Add(id))
                            return false;
                        if (type == EExecuteEvent.Update)
                            sTasks.Enqueue(command);
                        else if (type == EExecuteEvent.FixedUpdate)
                            sFixedTasks.Enqueue(command);
                        else
                            sLateTasks.Enqueue(command);
                        return true;
                    }
                }
            }
            return false;
        }

        public static bool RunOnMainThread(System.Action action, EExecuteEvent type = EExecuteEvent.Update, bool enableRepeatExecute = false)
        {
            if (action != null)
            {
                lock (sLock)
                {
                    var id = action.GetHashCode();
                    if (sExecuting)
                    {
                        var task = new Cmd0(action);// GetTask();
                        var buf = new ExecutionBuffer(id, type, task);
                        sExecBuffer = true;
                        return sBuffers.Add(buf);
                    }
                    else
                    {
                        var cache = type == EExecuteEvent.FixedUpdate ? sDuplicateFixedCache : sDuplicateTaskCache;
                        if (!enableRepeatExecute && !cache.Add(id))
                            return false;
                        var task = new Cmd0(action);
                        if (type == EExecuteEvent.Update)
                            sTasks.Enqueue(task);
                        else if (type == EExecuteEvent.FixedUpdate)
                            sFixedTasks.Enqueue(task);
                        else
                            sLateTasks.Enqueue(task);
                        return true;
                    }
                }
            }
            return false;
        }

        public static bool RunOnMainThread<T>(System.Action<T> action, T arg, EExecuteEvent type = EExecuteEvent.Update, bool enableRepeatExecute = false)
        {
            if (action != null)
            {
                lock (sLock)
                {
                    var id = action.GetHashCode();
                    id = (id << 5) - id + (arg == null ? 0 : arg.GetHashCode());
                    if (sExecuting)
                    {
                        var task = new Cmd1<T>(action, arg);// GetTaskWithArg<T>();
                        var buf = new ExecutionBuffer(id, type, task);
                        sExecBuffer = true;
                        return sBuffers.Add(buf);
                    }
                    else
                    {
                        var cache = type == EExecuteEvent.FixedUpdate ? sDuplicateFixedCache : sDuplicateTaskCache;
                        if (!enableRepeatExecute && !cache.Add(id))
                            return false;
                        var task = new Cmd1<T>(action, arg);
                        if (type == EExecuteEvent.Update)
                            sTasks.Enqueue(task);
                        else if (type == EExecuteEvent.FixedUpdate)
                            sFixedTasks.Enqueue(task);
                        else
                            sLateTasks.Enqueue(task);
                        return true;
                    }
                }
            }
            return false;
        }

        static void ExecuteTaskQueue(Queue<ICommand> taskQ)
        {
            while (true)
            {
                ICommand cmd = null;
                lock (sLock)
                {
                    if (taskQ.Count == 0)
                        break;
                    cmd = taskQ.Dequeue();
                }
                if (cmd != null)
                {
#if PROFILER
                    var context = cmd as IDebugContext;
                    Profiler.BeginSample(cmd.ToString(), context == null ? null : context.GetContext());
#endif
                    try
                    {
                        cmd.Execute();
                    }
                    catch (System.Exception e)
                    {
                        Debug.LogException(e
#if PROFILER
                                , context == null ? null : context.GetContext()
#endif
                                );
                    }
#if PROFILER
                    Profiler.EndSample();
#endif
                }
            }
        }

        static void ExecuteTask()
        {
            sExecuting = true;
            //SubThreading.MainUpdate();
            // execute tasks
            ExecuteTaskQueue(sTasks);

            MaterialCache.ExecuteUpdate();

            sExecuting = false;
        }

        static void ExecuteLateTask()
        {
            sExecuting = true;
            ExecuteTaskQueue(sLateTasks);
            lock (sLock)
            {
                sDuplicateTaskCache.Clear();
                if (sExecBuffer)
                {
                    sExecBuffer = false;
                    foreach (var buf in sBuffers)
                    {
                        var cache = buf.type == EExecuteEvent.FixedUpdate ? sDuplicateFixedCache : sDuplicateTaskCache;
                        if (cache.Add(buf.id))
                        {
                            if (buf.type == EExecuteEvent.Update)
                                sTasks.Enqueue(buf.cmd);
                            else if (buf.type == EExecuteEvent.FixedUpdate)
                                sFixedTasks.Enqueue(buf.cmd);
                            else
                                sLateTasks.Enqueue(buf.cmd);
                        }
                    }
                    sBuffers.Clear();
                }
            }
            //MaterialCache.ExecuteUpdate();
            sExecuting = false;
        }

        static void ExecuteFixedTask()
        {
            sExecuting = true;
            ExecuteTaskQueue(sFixedTasks);
            lock (sLock)
            {
                sDuplicateFixedCache.Clear();
            }
            sExecuting = false;
        }

        struct ExecutionBuffer : System.IEquatable<ExecutionBuffer>
        {
            public int id;
            public EExecuteEvent type;
            public ICommand cmd;
            public ExecutionBuffer(int id, EExecuteEvent type, ICommand cmd)
            {
                this.id = id;
                this.type = type;
                this.cmd = cmd;
            }

            public override int GetHashCode()
            {
                return id;
            }

            public override bool Equals(object obj)
            {
                if (obj is ExecutionBuffer)
                    return id == ((ExecutionBuffer)obj).id;
                else
                    return false;
            }

            public static bool operator ==(ExecutionBuffer a, ExecutionBuffer b)
            {
                return a.id == b.id;
            }

            public static bool operator !=(ExecutionBuffer a, ExecutionBuffer b)
            {
                return a.id != b.id;
            }

            public bool Equals(ExecutionBuffer other)
            {
                return this.id == other.id;
            }
        }

    }
}
